package view;

import controller.TrackModifiedListener;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import util.Images;
import util.NoSelectionButtonGroup;
import util.PaintUtilities;

/**
 * A TrackPanel displays a row of columns represent a beat each of which contain
 * a column of notes which can be placed or removed.
 */
public class TrackPanel extends JPanel {
	public static final int SMALL_COLUMN_WIDTH = 4;
	public static final int DEFAULT_HEIGHT = 32;

	private int highlightedIndex; //The index of the previous highlighted column
	private boolean paintBeat;	  //Whether or not the heart-beat effect should be painted

	public TrackPanel(int[] notes) {
		super(true);
		paintBeat = false;
		this.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));

		for(int i = 0; i < notes.length; i++) {
			JPanel column = new ColumnPanel();
			column.setPreferredSize(new Dimension(SMALL_COLUMN_WIDTH, DEFAULT_HEIGHT-1));

			ButtonGroup buttonGroup = new NoSelectionButtonGroup();
			for(int j = 1; j < 10; j++) {
				JToggleButton noteButton = new NoteButton();
				noteButton.setActionCommand(i+"_"+j);
				noteButton.setBorderPainted(false);
				column.add(noteButton);
				buttonGroup.add(noteButton);
			}

			if(notes[i] > 0) {
				((JToggleButton)column.getComponent(notes[i]-1)).setSelected(true);
			}
			this.add(column);
		}
	}

	/**
	 * Creates a TrackPanel identical to the given TrackPanel. When a note is changed
	 * on this panel, the change will be reflected on the given panel too but
	 * not vice versa.
	 */
	public TrackPanel(TrackPanel trackPanel) {
		super(true);
		this.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		this.setBorder(BorderFactory.createMatteBorder(1,1,0,0,Color.BLACK));

		Component[] components = trackPanel.getComponents();
		for(int i = 0; i < components.length; i++) {
			if(!(components[i] instanceof ColumnPanel)) continue;
			//Get the column
			JPanel trackColumn = (JPanel)components[i];

			JPanel column = new ColumnPanel();

			//Copy the button states.
			ButtonGroup buttonGroup = new NoSelectionButtonGroup();
			for(int j = 0; j < trackColumn.getComponentCount(); j++) {
				Component c = trackColumn.getComponent(j);
				if(!(c instanceof JToggleButton)) continue;
				JToggleButton trackNote = (JToggleButton)c;

				JToggleButton noteButton = new NoteButton();
				noteButton.addItemListener(new ChangeSynchroniser(trackNote));
				noteButton.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 1, Color.BLACK));

				column.add(noteButton);
				buttonGroup.add(noteButton);

				if(trackColumn.getComponent(j) instanceof JToggleButton) {
					if(((JToggleButton)trackColumn.getComponent(j)).isSelected()) {
						noteButton.setSelected(true);
					}
				}
			}
			this.add(column);
		}
	}

	public void setTrackChangeListener(TrackModifiedListener tcl) {
		//Add the listener to every button.
		for(Component c : this.getComponents()) {
			if(c instanceof JPanel) {
				for(Component b : ((JPanel)c).getComponents()) {
					if(b instanceof JToggleButton) {
						JToggleButton button = (JToggleButton)b;
						for(ItemListener il : button.getItemListeners()) {
							button.removeItemListener(il);
						}
						button.addItemListener(tcl);
					}
				}
			}
		}
	}

	public void highlightColumn(int columnIndex) {
		//Unhighlight the previous column first
		((JPanel)this.getComponent(highlightedIndex)).setEnabled(false);
		highlightedIndex = columnIndex;
		((JPanel)this.getComponent(highlightedIndex)).setEnabled(true);
	}

	/*
	 * Creates a LargeTrackPanel which reflects its changes back to this panel.
	 */
	public LargeTrackPanel enlarge() {
		return new LargeTrackPanel(this);
	}

	public void setPaintBeat(boolean paintBeat) {
		this.paintBeat = paintBeat;
	}

	public boolean getPaintBeat() {
		return this.paintBeat;
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		if(!paintBeat) return;

		Path2D path = new Path2D.Double();

		Graphics2D g2 = (Graphics2D)g;
		PaintUtilities.setUpGraphics(g2);
		//Start from the centre of the left end
		double midY = (double)this.getHeight()/2;
		path.moveTo(0, midY);

		//Create a buffer of activated notes to lay on top of the beat.
		BufferedImage activeNotesImage = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_ARGB);
		Graphics2D biG2 = activeNotesImage.createGraphics();
		PaintUtilities.setUpGraphics(biG2);

		for(int i = 0; i < this.getComponentCount(); i++) {
			JPanel column =  (JPanel)this.getComponent(i);
			boolean noteFound = false;
			for(Component c : column.getComponents()) {
				JToggleButton button = (JToggleButton)c;
				if(button.isSelected()) {
					//Add note marker to this location.
					biG2.drawImage(Images.getMarker(true),
						column.getX()+button.getX(), column.getY()+button.getY(),
						button.getWidth()-1, button.getHeight()-1
					, null);
					//Add a point to the centre of this button.
					path.lineTo(
						column.getX()+button.getX()+(button.getWidth()-1)/2,
						column.getY()+button.getY()+(button.getHeight()-1)/2
					);

					//Assume only one note can be places per column
					noteFound = true;
					break;
				}
			}
			if(column.isEnabled()) {
				//If it's the current column the seeker is at, stop.
				if(!noteFound) path.lineTo(column.getX() + (column.getWidth()-1)/2, midY);
				break;
			} else {
				//Add a point back to the middle
				path.lineTo(column.getX() + column.getWidth()-1, midY);
			}
		}

		biG2.dispose();

		//Create a large circle at the last point.
		Point2D lastPoint = path.getCurrentPoint();
		final double width = this.getComponent(0).getWidth()*3/4;
		Ellipse2D endCircle = new Ellipse2D.Double(
			lastPoint.getX()-width/2, lastPoint.getY()-width/2, width, width
		);

		//Finally, paint it.
		g2.setColor(new Color(0xDD882200, true));
		g2.setStroke(new BasicStroke((int)(width*1/4+1),
			BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND
		));

		g2.draw(path);
		g2.fill(endCircle);
		g2.drawImage(activeNotesImage, 0, 0,
			activeNotesImage.getWidth(), activeNotesImage.getHeight()
		, null);
	}



	/**
	 * Synchronises item events between this component and its synchronisee.
	 */
	private class ChangeSynchroniser implements ItemListener {

		private JToggleButton synchronisee;

		private ChangeSynchroniser(JToggleButton synchronisee) {
			this.synchronisee = synchronisee;
		}

		@Override
		public void itemStateChanged(ItemEvent e) {
			synchronisee.setSelected(e.getStateChange() == ItemEvent.SELECTED);
		}

	}
}
